<?php

// namespace mgxz\library;

/*
 * 数据库操作工具 表对象模型及驱动
 * @copyright mgxz.com
 * @author Longing
 * @version v1.0
 */

/*
 * 数据表模型
 * 注意：同一时刻同一个表不允许存在多个实例
 */

class wseTable {

    protected $dbKey, $table, $dbo, $sql, $page;

    /*
     * 获取指定表模型唯一实例
     * @param string $table 表名，完整格式为"数据库键名.表名 表别名"
     * @param string $prefix 可选，表前缀。如果不使用数据库默认前缀则必须设置此参数
     * @return object 返回表模型对象
     */

    public static function getInstance($table, $prefix = null, $tabHandle = TRUE) {
        static $tabs = array();
        $table = trim($table);
        $db_key = trim(strstr($table, '.', true));
        $dbo = wseDb::getInstance($db_key);
        if (!$db_key) {
            $db_key = $dbo->dbKey();
        } else {
            $table = trim(substr(strstr($table, '.'), 1));
        }
        $alias = '';
        if ($tabHandle) {
            $alias = trim(strstr($table, ' '));
            if ($alias) {
                $table = trim(strstr($table, ' ', true));
            }
        }
        if (is_null($prefix)) {
            $prefix = $dbo->prefix();
        }
        $tab_key = $db_key . $prefix . $table;
        if (!isset($tabs[$tab_key])) {
            $tabs[$table] = new wseTable($db_key, $prefix . $table);
        }
        return $tabs[$table]->init($alias, $tabHandle);
    }

    /*
     * @param string $db_key 数据库配置项键名，不可以为空字符串
     * @param string $table 完整表名
     */

    public function __construct($db_key, $table) {
        $this->dbKey = $db_key;
        $this->table = $table;
    }

    /*
     * @return object 返回引用的wseDb实例
     */

    public function getDbo() {
        return $this->dbo;
    }

    /*
     * 初始化，因使用了单例模式的原因，用于重置实例属性值
     * 使用T()函数或getInstance()方法时会自动调用此方法
     * @param string $alias 可选，表别名
     * @return object 返回wseTable实例自身
     */

    public function init($alias = '') {
        $this->dbo = wseDb::getInstance($this->dbKey);
        $this->sql = array(
            'select' => '*',
            'from' => $this->table,
            'join' => array(),
            'where' => array(),
            'set' => array(),
            'set_param' => array(),
            'group' => '',
            'order' => '',
            'limit' => '',
            'alias' => $alias,
            'param' => array(),
            'page' => false,
        );
        $this->page = array('page' => 0, 'size' => 0, 'total_page' => 0, 'total_count' => 0);
        return $this;
    }

    /*
     * 设置查询字段，一般不建议调用此方法，多次调用后，以最后一次调用为准
     * select(),getRow(),getCol(),getField()等方法可能会使此方法失效
     * @param string $fields 字段列表，与SELECT语句中的字段列表语法一致
     * @return object 返回wseTable实例自身
     */

    public function field($fields) {
        if($this->sql['select']!='*'){
            $this->sql['select'] = ','.trim($fields);
        }else{
            $this->sql['select'] = trim($fields);
        }
        return $this;
    }

    /*
     * 表设置别名
     * @param string $alias 别名
     * @return object 返回wseTable实例自身
     */

    public function alias($alias) {
        $this->sql['alias'] = $alias;
        return $this;
    }

    /*
     * 多表联接设置，可多次调用，表示多次联接
     * @param string $table 被联接的表名，默认为inner联接，可以带"left "与"right "前缀，分别表示左联接与右联接
     * @param string $prefix 可选，被联接的表前缀，不设置则使用默认前缀
     * @param string $condition 必填，联接条件表达式
     * @return object 返回wseTable实例自身
     */

    public function join($table, $prefix, $condition = null) {
        if (is_null($condition)) {//第二个参数($prefix)才是可选参数
            $condition = $prefix;
            $prefix = $this->dbo->prefix();
        } else {
            $prefix = trim($prefix);
        }
        $table = trim($table);
        if (strtolower(trim(substr($table, 0, 5))) == 'left') {
            $type = 'LEFT JOIN ';
            $table = $prefix . trim(substr($table, 5));
        } elseif (strtolower(trim(substr($table, 0, 6))) == 'right') {
            $type = 'RIGHT JOIN ';
            $table = $prefix . trim(substr($table, 6));
        } else {
            $type = 'JOIN ';
            $table = $prefix . $table;
        }
        $alias = trim(strstr($table, ' '));
        if ($alias) {
            $table = '`' . trim(strstr($table, ' ', true)) . '` AS ' . $alias;
        } else {
            $table = '`' . $table . '`';
        }
        $this->sql['join'][] = $type . $table . ' ON ' . $condition;
        return $this;
    }

    /*
     * 多表联嵌套子查询设置，可多次调用，表示多次联接
     * @param string $table 被联接的表名，默认为inner联接，可以带"left "与"right "前缀，分别表示左联接与右联接
     * @param string $prefix 可选，被联接的表前缀，不设置则使用默认前缀
     * @param string $condition 必填，联接条件表达式
     * @return object 返回wseTable实例自身
     */

    public function subQueryJoin($table, $prefix, $condition = null) {
        if (is_null($condition)) {//第二个参数($prefix)才是可选参数
            $condition = $prefix;
            $prefix = $this->dbo->prefix();
        } else {
            $prefix = trim($prefix);
        }
        $table = trim($table);
        if (strtolower(trim(substr($table, 0, 5))) == 'left') {
            $type = 'LEFT JOIN ';
            $table = $prefix . trim(substr($table, 5));
        } elseif (strtolower(trim(substr($table, 0, 6))) == 'right') {
            $type = 'RIGHT JOIN ';
            $table = $prefix . trim(substr($table, 6));
        } else {
            $type = 'JOIN  ';
            $table = $prefix . $table;
        }
        $alias = trim(strstr($table, ' '));
        if ($alias) {
            $table = trim(strstr($table, ' ', true)) . ' AS ' . $alias;
        }
        $this->sql['join'][] = $type . $table . ' ON ' . $condition;
        return $this;
    }

    /*
     * 查询条件设置，可多次调用，效果与一次调用时使用array参数一致
     * @param string $condition 查询条件，可以带有"?"的参数
     * @param array $condition 多项查询条件，可以带有"?"的参数。最终各项条件是以AND来连接的
     * @param mixed $param 可选，预处理SQL后需要用到的绑定参数数据，不为array时会转化成array的单个数据项
     * @return object 返回wseTable实例自身
     */

    public function where($condition, $param = null) {
        if (empty($condition)) {
            return $this;
        }
        if (is_array($condition)) {
            $this->sql['where'] = array_merge($this->sql['where'], $condition);
        } else {
            $this->sql['where'][] = trim($condition);
        }
        if (!is_null($param)) {
            if (is_array($param)) {
                $this->sql['param'] = array_merge($this->sql['param'], $param);
            } else {
                $this->sql['param'][] = $param;
            }
        }
        return $this;
    }

    /*
     * 更新数据设置，可多次调用
     * @param string $expression 更新表达式，可以带有"?"的参数，语法与UPDATE语句中的SET子句语法一致
     * @param array $expression 以字段名为键的一维数组，此时$param无效
     * @param mixed $param 可选，预处理SQL后需要用到的绑定参数数据，不为array时会转化成array的单个数据项
     * @return object 返回wseTable实例自身
     */

    public function set($expression, $param = null) {
        if (is_array($expression)) {
            foreach ($expression as $k => $v) {
                $this->sql['set'][] = $k . '=?';
                $this->sql['set_param'][] = $v;
            }
        } else {
            $this->sql['set'][] = $expression;
            if (!is_null($param)) {
                $this->sql['set_param'][] = $param;
            }
        }
        return $this;
    }

    /*
     * 分组设置，多次调用后，以最后一次调用为准
     * @param string $field 分组字段，语法与SELECT语句中的GROUP子句语法一致
     * @param string $having 可选，分组过滤表达式，语法与SELECT语句中的HAVING子句语法一致
     * @return object 返回wseTable实例自身
     */

    public function group($field, $having = null) {
        $this->sql['group'] = $field;
        if (!is_null($having)) {
            $this->sql['group'].=' HAVING ' . $having;
        }
        return $this;
    }

    /*
     * 排序设置，多次调用后，以最后一次调用为准
     * @param string $field 排序字段，语法与SELECT语句中的ORDER子句语法一致
     * @return object 返回wseTable实例自身
     */

    public function order($field) {
        $this->sql['order'] = $field;
        return $this;
    }

    /*
     * 返回数量限定设置，多次调用后，以最后一次调用为准，与setPage()方法互斥
     * 如果未调用此方法或setPage()方法，驱动类wseDb隐含最多返回1000条数据记录
     * @param int $start 可选，起始位置，从0开始
     * @param int $length 必须为正数，限定返回数据的条数
     * @return object 返回wseTable实例自身
     */

    public function limit($start, $length = 0) {
        if ($length) {//第一个参数$start才是可选参数
            $this->sql['limit'] = $start . ',' . $length;
        } else {
            $this->sql['limit'] = $start;
        }
        return $this;
    }

    /*
     * 返回数量限定设置，多次调用后，以最后一次调用为准
     * 与limit()方法互斥，调用此方法后将可以使用getPage()返回页码详情
     * @param int $page 当前页码值，从1开始
     * @param int $size 可选，页大小，默认20
     * @return object 返回wseTable实例自身
     */

    public function setPage($page, $size = 20) {
        $page = intval($page);
        $size = intval($size);
        if ($page < 1) {
            $page = 1;
        }
        $this->sql['limit'] = (($page - 1) * $size) . ',' . $size;
        $this->sql['page'] = true;
        $this->page['page'] = $page;
        $this->page['size'] = $size;
        return $this;
    }

    /*
     * 返回页面详细信息
     * 最后一次调用setPage()后，当时select()时的页码信息
     * @return array 页面详情，格式见init()中的定义
     */

    public function getPage() {
        return $this->page;
    }

    /*
     * SQL语句生成
     * @param array $arr 结构化的SQL信息，格式参见wseTable::init()方法的定义
     * @param string $type 可选，生成的SQL的类型 select(默认) update delete
     * @return mixed 完整的SQL语句。如果有使用了预处理特性，则返回二个元素的数组，第一项为SQL语句，第二项为绑定参数的数组
     */

    public function buildSql($type = 'select') {
        $sql=$this->dbo->buildSql($this->sql, $type);
        if(is_array($sql)){
            $sql[0]='('.$sql[0].')';
        }else{
            $sql='('.$sql.')';
        }
        return $sql;
    }

    /*
     * 执行SELECT查询
     * 执行本方法后调用getPage()才有意义
     * @param string $fields 格式与意义同field()
     * @return array 返回查询结果集，无数据时返回空数组
     */

    public function select($fields = null) {
        if (!is_null($fields)) {
            if($this->sql['select']!='*'){
                $this->sql['select'] = ','.trim($fields);
            }else{
                $this->sql['select'] = trim($fields);
            }
        }
        if ($this->sql['page']) {
            list($count, $arr) = $this->dbo->query($this->sql);
            $this->page['total_count'] = $count;
            $this->page['total_page'] = ceil($count / $this->page['size']);
        } else {
            $arr = $this->dbo->query($this->sql);
        }
        return $arr;
    }

    /*
     * 执行SELECT查询，返回一行数据
     * select()方法的快捷阉割版
     * @param string $fields 格式与意义同field()
     * @return array 返回查询结果集中的第一行，无数据时返回空数组
     */

    public function getRow($fields = null) {
        if (!is_null($fields)) {
            $this->sql['select'] = trim($fields);
        }
        $this->sql['limit'] = 1;
        $this->sql['page'] = false;
        $arr = $this->dbo->query($this->sql);
        return empty($arr) ? array() : $arr[0];
    }

    /*
     * 执行SELECT查询，返回一列数据
     * select()方法的快捷阉割版
     * @param string $column_key 单个需要返回的字段
     * @param string $index_key 可选，单个字段，用于作为返回数组的键名
     * @return array 返回查询结果集中的$column_key定义的列，无数据时返回空数组
     */

    public function getCol($column_key, $index_key = null) {
        if (is_null($index_key)) {
            $this->sql['select'] = trim($column_key);
        } else {
            $this->sql['select'] = trim($column_key) . ',' . trim($index_key);
        }
        $this->sql['page'] = false;
        $arr = $this->dbo->query($this->sql);
        $i=strpos($column_key,'.');
        $i && $column_key=substr($column_key,$i+1);
        if (empty($arr)) {
            return array();
        } elseif (is_null($index_key)) {
            return array_column($arr, $column_key);
        } else {
            $i=strpos($index_key,'.');
            $i && $index_key=substr($index_key,$i+1);
            return array_column($arr, $column_key, $index_key);
        }
    }

    /*
     * 执行SELECT查询，返回一行中某个字段的值
     * select()方法的快捷阉割版
     * @param string $field 单个字段，也可以是合计函数，一定不要定义字段别名
     * @return string 返回查询结果集中的个字段的值，无数据时返回空字符串
     */

    public function getField($field) {
        $this->sql['select'] = trim($field) . ' AS one';
        $this->sql['limit'] = 1;
        $this->sql['page'] = false;
        $arr = $this->dbo->query($this->sql);
        return empty($arr) ? '' : $arr[0]['one'];
    }

    /*
     * 执行数据插入查询
     * @param array $data 待插入的数据，$not_single为true时应该为二级数组，表示需要批量插入
     * @param boolean $not_single 可选，非单一插入标识，默认值false表示插入一条数据
     * @return int 返回成功插入的数据条数
     */

    public function insert($data, $not_single = false) {
        return $this->dbo->insert($this->table, $data, $not_single);
    }

    /*
     * 执行数据替换查询
     * @param array $data 待插入的数据，$not_single为true时应该为二级数组，表示需要批量插入
     * @param boolean $not_single 可选，非单一插入标识，默认值false表示插入一条数据
     * @return int 返回成功插入的数据条数
     */

    public function replace($data, $not_single = false) {
        return $this->dbo->replace($this->table, $data, $not_single);
    }

    /*
     * 执行数据更新查询
     * @return int 返回成功更新的数据条数
     */

    public function update($expression = null, $param = null) {
        if(!is_null($expression)){
            $this->set($expression, $param);
        }
        $param = array_merge($this->sql['set_param'], $this->sql['param']);
        return $this->dbo->update($this->table, $this->sql['set'], $this->sql['where'], $param);
    }

    /*
     * 执行数据删除查询
     * @return int 返回成功删除的数据条数
     */

    public function delete() {
        return $this->dbo->delete($this->table, $this->sql['where'], $this->sql['param']);
    }

    /*
     * 返回最后插入行的ID或序列值
     */

    public function lastId() {
        return $this->dbo->lastInsertId();
    }

    /*
     * 事务操作，可跨数据表模型调用
     * @param string $action 可选，事务动作，值为begin(默认) commit back
     * @return bool 返回是否操作成功
     */

    public function transaction($action = 'begin') {
        return $this->dbo->transaction($action);
    }

}

/*
 * 数据库驱动的简单封装
 * 功能包括生成SQL语句/简化查询等,目前仅支持pdo/mysql
 */

class wseDb {

    protected $dbKey, $dbh, $prefix = '';
    public static $dbs = array();
    public static $log = array();

    /*
     * 获取必要配置,移植时应修改成目标应用中的获取配置逻辑
     * @param string $key 配置项的键名
     * @return mixed 配置项的值
     */

    public static function getCfg($key) {
        static $cfg = null;
        if (is_null($cfg)) {
            $cfg = array(
                'db_deft'=>'main',
                'db_main'=>array( //业务库
                    'host'=>'rdsy3ol1z0nbwo7rid40.mysql.rds.aliyuncs.com',
                    'port'=>'3306',
                    'dbname'=>'mgxzwritedb',
                    'charset'=>'utf8',
                    'user'=>'mgxzdbwrite',
                    'pass'=>'bx110s_MS17',
                    'prefix'=>'',
                ),
                'db_user'=>array( //用户库
                    'host'=>'rdsy3ol1z0nbwo7rid40.mysql.rds.aliyuncs.com',
                    'port'=>'3306',
                    'dbname'=>'authoruser',
                    'charset'=>'utf8',
                    'user'=>'mgxzdbuser',
                    'pass'=>'ux110s_MS17',
                    'prefix'=>'',
                ),
                'db_report'=>array( //报表库
                    'host'=>'rds868559gezejkes6e2.mysql.rds.aliyuncs.com',
                    'port'=>'3306',
                    'dbname'=>'report',
                    'charset'=>'utf8',
                    'user'=>'report',
                    'pass'=>'MGby!@2017',
                    'prefix'=>'',
                ),
            );
        }
        return isset($cfg[$key]) ? $cfg[$key] : '';
    }

    /*
     * 获取指定数据库唯一实例
     * @param string $db_key 可选，在数据库配置中的键名，为空时将使用默认数据库
     * @return object 返回表数据库驱动实例
     */

    public static function getInstance($db_key = '') {
        if (empty($db_key)) {
            $db_key = trim(self::getCfg('db_deft'));
        } else {
            $db_key = trim($db_key);
        }
        if (empty(self::$dbs[$db_key])) {
            $cfg = self::getCfg('db_' . $db_key);
            if (empty($cfg['host']) || empty($cfg['dbname']) || empty($cfg['user'])) {
                trigger_error('DB config error', \E_USER_ERROR);
            }
            if (empty($cfg['port'])) {
                $cfg['port'] = '3306';
            }
            if (empty($cfg['charset'])) {
                $cfg['charset'] = 'utf8';
            }
            if (empty($cfg['prefix'])) {
                $cfg['prefix'] = '';
            }
            $cfg['key'] = $db_key;
            self::$dbs[$db_key] = new wseDb($cfg);
        }
        return self::$dbs[$db_key];
    }

    /*
     * 关闭引用的数据库连接
     * @param string $db_key 可选，在数据库配置中的键名，为空时将关闭全部的数据库连接
     * @return int 返回成功关闭的数量
     */

    public static function dbClose($db_key = null) {
        $n = 0;
        if (empty($db_key)) {
            foreach (self::$dbs as $dbo) {
                $t = $dbo->close();
                self::$dbs[$dbo->dbKey()] = null;
                if ($t)
                    $n++;
            }
        }else {
            $db_key = trim($db_key);
            if (!empty(self::$dbs[$db_key])) {
                $t = self::$dbs[$db_key]->close();
                self::$dbs[$db_key] = null;
                if ($t)
                    $n++;
            }
        }
        return $n;
    }

    /*
     * @param array $cfg 配置信息，约定配置信息必须是完整的
     */

    public function __construct($cfg) {
        $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', $cfg['host'], $cfg['port'], $cfg['dbname'], $cfg['charset']);
        try {
            $conn = new \PDO($dsn, $cfg['user'], $cfg['pass']);
        } catch (\PDOException $e) {
            trigger_error('Connection failed: ' . $e->getMessage(), \E_USER_ERROR);
        }
        $this->dbKey = $cfg['key'];
        $this->dbh = $conn;
        $this->prefix = $cfg['prefix'];
    }

    /*
     * 获取引用数据库在配置中的键名
     * @return string
     */

    public function dbKey() {
        return $this->dbKey;
    }

    /*
     * 获取原生数据库连接对象
     * @return object 返回PDO对象
     */

    public function handle() {
        return $this->dbh;
    }

    /*
     * 获取数据表默认前缀
     * 本类中不会对表名自动加前缀，主要为上层业务类
     * @return string 返回表前缀
     */

    public function prefix() {
        return $this->prefix;
    }

    /*
     * SQL语句生成
     * @param array $arr 结构化的SQL信息，格式参见wseTable::init()方法的定义
     * @param string $type 可选，生成的SQL的类型 select(默认) update delete
     * @return mixed 完整的SQL语句。如果有使用了预处理特性，则返回二个元素的数组，第一项为SQL语句，第二项为绑定参数的数组
     */

    public function buildSql($arr, $type = 'select') {
        $table = $arr['from'];
        if (!empty($arr['alias'])) {
            $table.=" AS " . $arr['alias'];
        }
        $where = empty($arr['where']) ? '' : (' WHERE ' . implode(' AND ', $arr['where']));
        $param = $arr['param'];
        if ($type == 'select') {
            if (!empty($arr['join'])) {
                $table.=' ' . implode(' ', $arr['join']);
            }
            $sql = 'SELECT ' . $arr['select'] . ' FROM ' . $table . $where
                    . (empty($arr['group']) ? '' : (' GROUP BY ' . $arr['group']))
                    . (empty($arr['order']) ? '' : (' ORDER BY ' . $arr['order']))
                    . (empty($arr['limit']) ? ' LIMIT 5000' : (' LIMIT ' . $arr['limit']));
        } elseif ($type == 'update') {
            $param = array_merge($arr['set_param'], $arr['param']);
            $sql = 'UPDATE `' . $table . '` SET ' . implode(',', $arr['set']) . $where;
        } elseif ($type == 'delete') {
            $sql = 'DELETE FROM `' . $table . '`' . $where;
        } else {
            $sql = '';
        }
        return empty($param) ? $sql : array($sql, $param);
    }

    /*
     * 查询数据库
     * @param array $sql 结构化的SELECT语句，此时第二参数$param无效
     * @param string $sql 完整的SELECT语句
     * @param array $param 可选，预处理SQL后需要用到的绑定参数数据，数组需要以连续数字为索引
     * @return array 无有效数据时返回空数组，否则是二级数组
     */

    public function query($sql, $param = null) {
        $count = -1;
        if (is_array($sql)) {
            $param = $sql['param'];
            $table = $sql['from'];
            if (!empty($sql['alias'])) {
                $table.= " AS " . $sql['alias'];
            }
            if (!empty($sql['join'])) {
                $table.=' ' . implode(' ', $sql['join']);
            }
            $where = empty($sql['where']) ? '' : (' WHERE ' . implode(' AND ', $sql['where']));
            $group = empty($sql['group']) ? '' : (' GROUP BY ' . $sql['group']);
            $order = empty($sql['order']) ? '' : (' ORDER BY ' . $sql['order']);
            $limit = empty($sql['limit']) ? ' LIMIT 5000' : (' LIMIT ' . $sql['limit']);
            if ($sql['page']) {
                $sql_count = 'SELECT Count(1) FROM ' . $table . $where . $group;
                self::$log[] = $sql_count;
                $sth = $this->dbh->prepare($sql_count);
                if (empty($param)) {
                    $sth->execute();
                } else {
                    $sth->execute($param);
                }
                $err = $sth->errorCode();
                if ($err != '00000') {
                    $err = $sth->errorInfo();
                    trigger_error($err[2], \E_USER_ERROR);
                }
                $count = intval($sth->fetchColumn());
            }
            $sql = 'SELECT ' . $sql['select'] . ' FROM ' . $table . $where . $group . $order . $limit;
        }
        self::$log[] = $sql;
        if ($count != 0) {
            $sth = $this->dbh->prepare($sql);
            if (empty($param)) {
                $sth->execute();
            } else {
                $sth->execute($param);
            }
            $err = $sth->errorCode();
            if ($err != '00000') {
                $err = $sth->errorInfo();
                trigger_error($err[2], \E_USER_ERROR);
            }
            $arr = $sth->fetchAll(\PDO::FETCH_ASSOC);
        }
        if (empty($arr)) {
            $arr = array();
        }
        return $count < 0 ? $arr : array($count, $arr);
    }

    /*
     * 执行任意非返回结果集的查询
     * @param string $sql 完整的SQL语句
     * @param array $param 可选，预处理SQL后需要用到的绑定参数数据，数组需要以连续数字为索引
     * @return int 返回受影响的数据行数
     */

    public function exec($sql, $param = null) {
        self::$log[] = $sql;
        $sth = $this->dbh->prepare($sql);
        $re = empty($param) ? $sth->execute() : $sth->execute($param);
        $err = $sth->errorCode();
        if ($err != '00000') {
            $err = $sth->errorInfo();
            trigger_error($err[2], \E_USER_ERROR);
        }
        return intval($re);
    }

    /*
     * 执行数据插入查询
     * @param string $table 完整的表名
     * @param array $data 待插入的数据，$not_single为true时应该为二级数组，表示需要批量插入
     * @param bool $not_single 可选，非单一插入标识，默认值false表示插入一条数据
     * @return int 返回成功插入的条数
     */

    public function insert($table, $data, $not_single = false) {
        $re = 0;
        if (empty($data) || !is_array($data)) {
            return $re;
        }
        if ($not_single) {
            $keys = array_keys($data[0]);
        } else {
            $keys = array_keys($data);
            $data = array($data);
        }
        $sql = 'INSERT INTO `' . $table . '` (' . implode(',', $keys) . ') VALUES (' . implode(',', array_fill(0, count($keys), '?')) . ')';
        self::$log[] = $sql;
        $sth = $this->dbh->prepare($sql);
        foreach ($data as $row) {
            $arr = array();
            foreach ($row as $v) {
                $arr[] = $v;
            }
            $re+=intval($sth->execute($arr));
            $err = $sth->errorCode();
            if ($err != '00000') {
                $err = $sth->errorInfo();
                trigger_error($err[2], \E_USER_ERROR);
            }
        }
        return $re;
    }
    
    /*
     * 执行数据替换查询
     * @param string $table 完整的表名
     * @param array $data 待插入的数据，$not_single为true时应该为二级数组，表示需要批量插入
     * @param bool $not_single 可选，非单一插入标识，默认值false表示插入一条数据
     * @return int 返回成功插入的条数
     */

    public function replace($table, $data, $not_single = false) {
        $re = 0;
        if (empty($data) || !is_array($data)) {
            return $re;
        }
        if ($not_single) {
            $keys = array_keys($data[0]);
        } else {
            $keys = array_keys($data);
            $data = array($data);
        }
        $sql = 'REPLACE INTO `' . $table . '` (' . implode(',', $keys) . ') VALUES (' . implode(',', array_fill(0, count($keys), '?')) . ')';
        self::$log[] = $sql;
        $sth = $this->dbh->prepare($sql);
        foreach ($data as $row) {
            $arr = array();
            foreach ($row as $v) {
                $arr[] = $v;
            }
            $re+=intval($sth->execute($arr));
            $err = $sth->errorCode();
            if ($err != '00000') {
                $err = $sth->errorInfo();
                trigger_error($err[2], \E_USER_ERROR);
            }
        }
        return $re;
    }

    /*
     * 数据更新查询
     * @param string $table 完整的表名
     * @param array $set 待更新的数据表达式数组
     * @param string $set 待更新的完整数据表达式
     * @param array $where 更新条件表达式数组，条件之间以最终是以AND连接
     * @param string $where 更新条件的完整表达式，可以为空字符串
     * @param array $param 可选，预处理SQL后需要用到的绑定参数数据，数组需要以连续数字为索引
     * @return int 返回受影响的数据行数
     */

    public function update($table, $set, $where, $param = null) {
        if (is_array($set)) {
            $set = implode(',', $set);
        }
        if (is_array($where)) {
            $where = implode(' AND ', $where);
        }
        $where = empty($where) ? '' : (' WHERE ' . $where);
        $sql = 'UPDATE `' . $table . '` SET ' . $set . $where;
        self::$log[] = $sql;
        $sth = $this->dbh->prepare($sql);
        $re = empty($param) ? $sth->execute() : $sth->execute($param);
        $err = $sth->errorCode();
        if ($err != '00000') {
            $err = $sth->errorInfo();
            trigger_error($err[2], \E_USER_ERROR);
        }
        return intval($re);
    }

    /*
     * 数据删除查询
     * @param string $table 完整的表名
     * @param array $where 更新条件表达式数组，条件之间以最终是以AND连接
     * @param string $where 更新条件的完整表达式，可以为空字符串
     * @param array $param 可选，预处理SQL后需要用到的绑定参数数据，数组需要以连续数字为索引
     * @return int 返回受影响的数据行数
     */

    public function delete($table, $where, $param = null) {
        if (is_array($where)) {
            $where = implode(' AND ', $where);
        }
        $where = empty($where) ? '' : (' WHERE ' . $where);
        $sql = 'DELETE FROM `' . $table . '`' . $where;
        self::$log[] = $sql;
        $sth = $this->dbh->prepare($sql);
        $re = empty($param) ? $sth->execute() : $sth->execute($param);
        $err = $sth->errorCode();
        if ($err != '00000') {
            $err = $sth->errorInfo();
            trigger_error($err[2], \E_USER_ERROR);
        }
        return intval($re);
    }

    /*
     * 返回最后插入行的ID或序列值
     */

    public function lastInsertId() {
        return $this->dbh->lastInsertId();
    }

    /*
     * 事务操作
     * @param string $action 可选，事务动作，值为begin(默认) commit back
     * @return bool 返回是否操作成功
     */

    public function transaction($action = 'begin') {
        if ($action == 'begin') {
            return $this->dbh->beginTransaction();
        } elseif ($action == 'commit') {
            return $this->dbh->commit();
        } elseif ($action == 'back') {
            return $this->dbh->rollBack();
        } else {
            return false;
        }
    }

    /*
     * 关闭数据库连接
     * 本方法供静态方法dbClose()调用，在其它地方不要手动调用它。
     */

    public function close() {
        $this->dbh = null;
        return true;
    }

}
